package com.wx.io.multichannel;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 多路复用器 selector
 * 单线程版本
 * 带读写的版本
 * 2020年12月31日10:19:46
 */
public class SingleSelectorV1_1 {

    ServerSocketChannel server = null;
    Selector selector = null;
    int port = 9999;

    public void initServer(){
        try {
            // 开启Nio Server
            server = ServerSocketChannel.open();
            // 配置非阻塞
            server.configureBlocking(false);
            // binding port
            server.bind(new InetSocketAddress(port));

            /**
             * 开启多路选择器
             * server 的listen文件描述符假设是fd4
             * select poll 模型下是相当于在jvm 中开辟一块空间，来存储这些开启的文件描述符fd4
             * epoll
             *       模型下 是调用内核函数 epoll_create(fd3,fd4,add) 将fd4放到fd3中，形成红黑树，用于观察这些
             *       文件描述符，当对应的文件描述符发生状态的变化，内核就会利用中断的回调方法，对比红黑树，看看是否是线程需要
             *      观察的文件描述符，如果符合，那么就会将此文件描述符放到，链表中。等待select方法的调用。一次性的取走所有有
             *      变化的文件描述符，用户线程在依次对有变化的文件描述符进行读写的操作
             */
            selector = Selector.open();

            server.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() throws IOException {
        initServer();
        while (true){
            // 文件描述符
            Set<SelectionKey> keys = selector.keys();
            System.out.println("keys size:"+keys.size());
            /**
             * select(time_out) 超时时间，不设置为一直阻塞
             * 调用select  相当于 select(fds) poll(fds) epoll_wait()
             */
            if(selector.select()>0){
                // 状态发生改变的文件描述符
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                while (keyIterator.hasNext()){
                    SelectionKey key = keyIterator.next();
                    keyIterator.remove();

                    if(key.isAcceptable()){ //文件描述符是连接
                        acceptHandler(key);
                    }else if(key.isReadable()){ // 可读
                        readHandler(key);
                    }else if(key.isWritable()){ //可写
                        writeHandler(key);

                    }
                }
            }
        }

    }



    private void acceptHandler(SelectionKey key) throws IOException {
        ServerSocketChannel channel =(ServerSocketChannel) key.channel();
        SocketChannel client = channel.accept();
        client.configureBlocking(false);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096);

        client.register(selector, SelectionKey.OP_READ, byteBuffer);
        System.out.println("-------------------------------------------");
        System.out.println("新客户端：" + client.getRemoteAddress());
        System.out.println("-------------------------------------------");
    }
    private void readHandler(SelectionKey key) throws IOException {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        while (true){
            int num = client.read(buffer);
            if(num>0){ //有值
                // 创建可写的文件描述符,注册到selector
                // 作为附件传入,到write的时候
                client.register(selector,SelectionKey.OP_WRITE,buffer);
            }else if(num == 0){
                break;
            }else{
                client.close();
                break;
            }
        }

    }

    private void writeHandler(SelectionKey key) throws IOException {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer)key.attachment();
        buffer.put("respect".getBytes());
        buffer.flip();
        if(buffer.hasRemaining()){
            client.write(buffer);
        }
        buffer.clear();
        //key.cancel();
        client.register(selector,SelectionKey.OP_READ,buffer);

    }

    public static void main(String[] args) {
        try {
            new SingleSelectorV1_1().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
